/*
 * Copyright (c) Kumo Inc. and affiliates.
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <stdexcept>

#include <melon/cportability.h>
#include <melon/conv.h>
#include <melon/likely.h>
#include <melon/portability.h>
#include <melon/range.h>
#include <melon/lang/exception.h>

namespace melon {
    struct FormatArg;

    class MELON_EXPORT BadFormatArg : public std::invalid_argument {
    private:
        friend struct FormatArg;

        struct ErrorStrTag {
        };

        template<typename... A>
        static std::string str(StringPiece descr, A const &... a) {
            return to<std::string>(
                "invalid format argument {"_sp, descr, "}: "_sp, a...);
        }

    public:
        using invalid_argument::invalid_argument;

        template<typename... A>
        explicit BadFormatArg(ErrorStrTag, StringPiece descr, A const &... a)
            : invalid_argument(str(descr, a...)) {
        }
    };

    /**
     * Parsed format argument.
     */
    struct FormatArg {
        /**
         * Parse a format argument from a string.  Keeps a reference to the
         * passed-in string -- does not copy the given characters.
         */
        explicit FormatArg(StringPiece sp)
            : fullArgString(sp),
              fill(kDefaultFill),
              align(Align::DEFAULT),
              sign(Sign::DEFAULT),
              basePrefix(false),
              thousandsSeparator(false),
              trailingDot(false),
              width(kDefaultWidth),
              widthIndex(kNoIndex),
              precision(kDefaultPrecision),
              presentation(kDefaultPresentation),
              nextKeyMode_(NextKeyMode::NONE) {
            if (!sp.empty()) {
                initSlow();
            }
        }

        enum class Type {
            INTEGER,
            FLOAT,
            OTHER,
        };

        /**
         * Validate the argument for the given type; throws on error.
         */
        void validate(Type type) const;

        /**
         * Throw an exception if the first argument is false.  The exception
         * message will contain the argument string as well as any passed-in
         * arguments to enforce, formatted using melon::to<std::string>.
         */
        template<typename Check, typename... Args>
        void enforce(Check const &v, Args &&... args) const {
            static_assert(std::is_constructible<bool, Check>::value, "not castable");
            if (MELON_UNLIKELY(!v)) {
                error(static_cast<Args &&>(args)...);
            }
        }

        template<typename... Args>
        [[noreturn]] void error(Args &&... args) const;

        /**
         * Full argument string, as passed in to the constructor.
         */
        StringPiece fullArgString;

        /**
         * Fill
         */
        static constexpr char kDefaultFill = '\0';
        char fill;

        /**
         * Alignment
         */
        enum class Align : uint8_t {
            DEFAULT,
            LEFT,
            RIGHT,
            PAD_AFTER_SIGN,
            CENTER,
            INVALID,
        };

        Align align;

        /**
         * Sign
         */
        enum class Sign : uint8_t {
            DEFAULT,
            PLUS_OR_MINUS,
            MINUS,
            SPACE_OR_MINUS,
            INVALID,
        };

        Sign sign;

        /**
         * Output base prefix (0 for octal, 0x for hex)
         */
        bool basePrefix;

        /**
         * Output thousands separator (comma)
         */
        bool thousandsSeparator;

        /**
         * Force a trailing decimal on doubles which could be rendered as ints
         */
        bool trailingDot;

        /**
         * Field width and optional argument index
         */
        static constexpr int kDefaultWidth = -1;
        static constexpr int kDynamicWidth = -2;
        static constexpr int kNoIndex = -1;
        int width;
        int widthIndex;

        /**
         * Precision
         */
        static constexpr int kDefaultPrecision = -1;
        int precision;

        /**
         * Presentation
         */
        static constexpr char kDefaultPresentation = '\0';
        char presentation;

        /**
         * Split a key component from "key", which must be non-empty (an exception
         * is thrown otherwise).
         */
        template<bool emptyOk = false>
        StringPiece splitKey();

        /**
         * Is the entire key empty?
         */
        bool keyEmpty() const {
            return nextKeyMode_ == NextKeyMode::NONE && key_.empty();
        }

        /**
         * Split an key component from "key", which must be non-empty and a valid
         * integer (an exception is thrown otherwise).
         */
        int splitIntKey();

        void setNextIntKey(int val) {
            assert(nextKeyMode_ == NextKeyMode::NONE);
            nextKeyMode_ = NextKeyMode::INT;
            nextIntKey_ = val;
        }

        void setNextKey(StringPiece val) {
            assert(nextKeyMode_ == NextKeyMode::NONE);
            nextKeyMode_ = NextKeyMode::STRING;
            nextKey_ = val;
        }

    private:
        void initSlow();

        template<bool emptyOk>
        StringPiece doSplitKey();

        StringPiece key_;
        int nextIntKey_;
        StringPiece nextKey_;

        enum class NextKeyMode {
            NONE,
            INT,
            STRING,
        };

        NextKeyMode nextKeyMode_;
    };

    template<typename... Args>
    [[noreturn]] inline void FormatArg::error(Args &&... args) const {
        // take advantage of throw_exception decaying char const (&)[N} to char const*
        // as a special case of the facility
        throw_exception<BadFormatArg>(
            BadFormatArg::ErrorStrTag{}, fullArgString, static_cast<Args &&>(args)...);
    }

    template<bool emptyOk>
    inline StringPiece FormatArg::splitKey() {
        enforce(nextKeyMode_ != NextKeyMode::INT, "integer key expected");
        return doSplitKey<emptyOk>();
    }

    template<bool emptyOk>
    inline StringPiece FormatArg::doSplitKey() {
        if (nextKeyMode_ == NextKeyMode::STRING) {
            nextKeyMode_ = NextKeyMode::NONE;
            if (!emptyOk) {
                // static
                enforce(!nextKey_.empty(), "non-empty key required");
            }
            return nextKey_;
        }

        if (key_.empty()) {
            if (!emptyOk) {
                // static
                error("non-empty key required");
            }
            return StringPiece();
        }

        const char *b = key_.begin();
        const char *e = key_.end();
        const char *p;
        if (e[-1] == ']') {
            --e;
            p = static_cast<const char *>(memchr(b, '[', size_t(e - b)));
            enforce(p != nullptr, "unmatched ']'");
        } else {
            p = static_cast<const char *>(memchr(b, '.', size_t(e - b)));
        }
        if (p) {
            key_.assign(p + 1, e);
        } else {
            p = e;
            key_.clear();
        }
        if (!emptyOk) {
            // static
            enforce(b != p, "non-empty key required");
        }
        return StringPiece(b, p);
    }

    inline int FormatArg::splitIntKey() {
        if (nextKeyMode_ == NextKeyMode::INT) {
            nextKeyMode_ = NextKeyMode::NONE;
            return nextIntKey_;
        }
        auto result = tryTo<int>(doSplitKey<true>());
        enforce(result, "integer key required");
        return *result;
    }
} // namespace melon
